Golang基础进阶

您所在的位置:网站首页 golang 切片 并发安全 Golang基础进阶

Golang基础进阶

2023-10-24 21:33| 来源: 网络整理| 查看: 265

Golang基础进阶——并发Map和List sync.Map

Go 语言中 map 在并发情况下,只读是线程安全的,同时读写线程不安全。下面来看下并发情况下读 map 出现的问题,示例:

func main() { for { m := make(map[int]int) // 开启一段并发代码 go func() { // 不停地对map进行写入 for { m[1] = 1 } }() go func() { // 不停地对map进行读取 for { _ = m[1] } }() } } // fatal error: concurrent map read and map write

运行时输出提示并发 map 读写。 也就是说使用了两个并发函数不断地对map行读和写而发生了竞态问题。 map 内部对这种并发操作进行检查井提前发现。

需要并发读写 ,一般的做法是加锁,但这样性能并不高。 Go 语言 1.9 本中提了一种效率较高的并发安全 sync.Map。sync.Map 和 map 不同,不是以语言原生形态提供,而是在 sync 包下特殊结构。

sync.Map 以下特性:

无需初始化,直接声明即可。

sync.Map 不能使用 map 方式进行取值和设置等操作,而是使用 sync.Map 方法进行调用 Store 表示存储,Load 表示获取 Delete 表示删除。使用 Range 配合一个回调函数进行遍历操作,通过回调函数返回内部遍历出来的值。Range 参数中的回调函数的返回值功能是:需要继续迭代遍历时,返回 true;终止迭代遍历时,返回 false。

示例: func main() { // 声明 scene 类型为 sync.Map 。注意,sync Map 不能使用 make 创建。 var scene sync.Map // 保存键值,是以interface{}类型进行保存 scene.Store("green",97) scene.Store("london",100) scene.Store("egypt",200) // 从sync.Map中根据键取值 fmt.Println(scene.Load("london")) //根据键删除对应的键值对 scene.Delete("london") scene.Range(func(key, value interface{}) bool { fmt.Println("iterate: ",key, value) return true }) }

sync 没有提供获取 map 数的方法,替代方法是获取时遍历自行计算数量。 sync.Map为了保证并发安全有一些性能损失,因此在非并发情况下,使用 map 相比使用 sync.Map会有更好的性能。

 列表( list )————可以快速增删的非连续空间的容器 概述

列表是一种非连续存储的容器,由多个节点组成,节点通过一些变量记录彼此之间的关系。列表有很多种实现方法,如单链表、双链表等。

列表的原理可以这样理解:假设A、B、C个人都有电话号码,如果A把号码告诉B, B把号码告诉给C ,这个过程就建立了y一个单链表结构,如图所示:

 

如果在这个基础上,再从C开始将自己的号码给自己知道号码的人,这样就形成了双链表结构,如图所示:

 

那么如果需要获得所有人的号码,只需要从A或者C开始,要求他们将自己的号码发出来,然后再通知下一个人如此循环。这个过程就是列表遍历。

如果B换号码了,他需要通知A和C,将自己的号码移除。这个过程就是列表元素的删除操作,如图所示:

在 Go 语言中,将列表使用 container/list 包来实现,内部的实现原理是双链表。列表高效地进行任意位置的元素插入和删除操作。

初始化列表

list 的初始化有两种方法: New 和声明。两种方法的初始化效果都是一致的。

1. 通过container/list包的New方法初始化list 变量名 := list.New() 2. 通过声明初始化list var 变量名 list.List

列表与切片和map不同的是,列表并没有具体元素类型的限制。因此,列表的元素可以是任意类型。这既带来便利,也会引来一些问题。给一个列表放入了非期望类型的值,在取出值后,将 interface{} 转换为期望类型时将会发生岩机。

在列表中插入元素

双链表支持从队列前方或后方插入元素,分别对应的方法是 PushFront 和 PushBack提示:这两个方法都会返回一个 *list.Element 结构。如果在以后的使用中需要删除插入的元素,则只能通过*list.Element 配合Remove()方法进行删除这种方法可以让删除更加效率化,也是双链表特性之一。

 下面代码展示如何给 list 添加元素:

func main() { l := list.New() // 将 first 字符串插入到列表的尾部,此时列表是空的,插入后只有一个元素 l.PushBack("first") // 将数值67放入列表。此时,列表中己经存在 first 元素, 67 这个元素将被放在 fist 的前面 l.PushFront("67") } 列表插入元素的方法如表所示。

 

从列表中删除元素

列表的插入函数的返回值会提供一个 *list.Element结构,这个结构记录着列表元素的值及和其他节点之间的关系等信息。 从列表中删除元素时,需要用到这个结构进行快速删除。

func main() { l := list.New() // 尾部添加 l.PushBack("canon") // 头部添加67 l.PushFront(67) // 尾部添加后保存元素句柄,并将这个元素的内部结构保存到 lement 变量中 element := l.PushBack("first") // 在first之后添加high l.InsertAfter("heigh", element) // 在“fist”之前添加”noon” l.InsertBefore("noon", element) // 移除 element 变量对应的元素 l.Remove(element) }

下面列表展示了每次操作列表的实际使用情况。

 

 

遍历列表——访问列表的每一个元素

 遍历双链表需要配合 Front() 函数获取头元素,遍历时只要元素不为空就可以继续进行。每一次遍历调用元素的 Next,示例:

func main() { l := list.New() l.PushBack("canon") l.PushFront(67) for i := l.Front(); i != nil; i = i.Next() { fmt.Println(i.Value) } }

使用 for 语句进行遍历,其中 i:= I.Front() 表示初始赋值,只会在一开始执行一次; 每次循环会进行 i != nil 语句判断,如果返回 false ,表示退出循环,反之则会执行 i = i.Next()。使用遍历返回的 *list.Element 的Value 成员取得放入列表时的原值。

 



【本文地址】


今日新闻


推荐新闻


CopyRight 2018-2019 办公设备维修网 版权所有 豫ICP备15022753号-3